// ----------------------------------------------------------------------------
// Copyright (C) 2003 Rafael H. Bordini, Jomi F. Hubner, et al.
// 
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// 
// To contact the authors:
// http://www.inf.ufrgs.br/~bordini
// http://www.das.ufsc.br/~jomi
//
//
//----------------------------------------------------------------------------

package jason.asSyntax;

import jason.asSemantics.Unifier;
import jason.asSyntax.PlanBody.BodyType;
import jason.asSyntax.parser.ParseException;
import jason.asSyntax.parser.as2j;

import java.io.StringReader;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

/** 
  Represents an AgentSpeak trigger (like +!g, +p, ...).
  
  It is composed by:
     an operator (+ or -);
     a type (<empty>, !, or ?);
     a literal
 
  (it extends structure to be used as a term)
   
  @opt attributes    
  @navassoc - literal  - Literal
  @navassoc - operator - TEOperator
  @navassoc - type     - TEType
*/
public class Trigger extends Structure implements Cloneable {

    private static Logger logger = Logger.getLogger(Trigger.class.getName());
      
    public enum TEOperator { 
        add        { public String toString() { return "+"; } }, 
        del        { public String toString() { return "-"; } },
        goalState  { public String toString() { return "^"; } },
    };
    
    public enum TEType { 
        belief  { public String toString() { return ""; } }, 
        achieve { public String toString() { return "!"; } }, 
        test    { public String toString() { return "?"; } }
    };
    
    
    private TEOperator operator = TEOperator.add;
    private TEType     type     = TEType.belief;
    private Literal    literal;

    private boolean     isTerm = false; // it is true when the plan body is used as a term instead of an element of a plan

    public Trigger(TEOperator op, TEType t, Literal l) {
        super("te", 0);
        literal = l;
        type    = t;
        setTrigOp(op);
        setSrcInfo(l.getSrcInfo());
    }

    /** prefer to use ASSyntax.parseTrigger */
    public static Trigger parseTrigger(String sTe) {
        as2j parser = new as2j(new StringReader(sTe));
        try {
            return parser.trigger(); 
        } catch (Exception e) {
            logger.log(Level.SEVERE,"Error parsing trigger" + sTe,e);
            return null;
        }
    }

    // override some structure methods
    @Override
    public int getArity() {
        return 2;
    }

    @Override
    public Term getTerm(int i) {
        switch (i) {
        case 0: return new StringTermImpl(operator.toString() + type.toString()); 
        case 1: return literal;
        default: return null;
        }
    }
    
    @Override
    public void setTerm(int i, Term t) {
        switch (i) {
        case 0: 
            logger.warning("setTerm(i,t) for i=0 -- the operator -- , IS NOT IMPLEMENTED YET!!!");
            break;
        case  1: literal = (Literal)t; break;
        }
    }
    
    public void setTrigOp(TEOperator op) {
        operator = op;
        predicateIndicatorCache  = null;
    }
    

    public boolean sameType(Trigger e) {
        return operator == e.operator && type == e.type;
    }

    @Override
    public boolean equals(Object o) {
        if (o != null && o instanceof Trigger) {
            Trigger t = (Trigger) o;
            return (operator == t.operator && type == t.type && literal.equals(t.getLiteral()));
        }
        return false;
    }

    public boolean isAchvGoal() {
        return type == TEType.achieve;
    }

    public boolean isGoal() {
        return type == TEType.achieve || type == TEType.test;
    }    

    public boolean isMetaEvent() {
        return operator == TEOperator.goalState;
    }
    
    public TEOperator getOperator() {
        return operator;
    }
    
    public TEType getType() {
        return type;
    }

    public boolean isAddition() {
        return operator == TEOperator.add;
    }

    public Trigger clone() {
        Trigger c = new Trigger(operator, type, literal.copy());
        c.predicateIndicatorCache = this.predicateIndicatorCache;
        c.isTerm = isTerm;
        return c; 
    }   

    @Override
    public Trigger capply(Unifier u) {
        Trigger c = new Trigger(operator, type, (Literal)literal.capply(u));
        c.predicateIndicatorCache = this.predicateIndicatorCache;
        c.isTerm = isTerm;
        return c; 
    }

    
    /** return [+|-][!|?] super.getPredicateIndicator */
    @Override
    public PredicateIndicator getPredicateIndicator() {
        if (predicateIndicatorCache == null) {
            predicateIndicatorCache = new PredicateIndicator(operator.toString() + type + literal.getFunctor(), literal.getArity());
        }
        return predicateIndicatorCache;
    }
    
    /*public boolean apply(Unifier u) {
        return literal.apply(u);
    }*/

    public Literal getLiteral() {
        return literal;
    }

    public void setLiteral(Literal literal) {
        this.literal = literal;
        predicateIndicatorCache = null;
    }

    public void setAsTriggerTerm(boolean b) {
        isTerm = b;
    }
    
    public String toString() {
        String b, e;
        if (isTerm) {
            b = "{ "; 
            e = " }";
        } else {
            b = ""; 
            e = "";
        }
        return b + operator+ type + literal + e;
    }
    
    /** try to convert the term t into a trigger, in case t is a trigger term, a string that can be parsed to a trigger, a var with value trigger, .... */
    public static Trigger tryToGetTrigger(Term t) throws ParseException {
        if (t instanceof Trigger) {
            return (Trigger)t;
        }
        /*if (t instanceof VarTerm) {
            VarTerm v = (VarTerm)t;
            if (v.hasValue() && v.getValue() instanceof Trigger) {
                return (Trigger)v.getValue();            
            }
            if (v.hasValue() && v.getValue() instanceof Plan) {
                return ((Plan)v.getValue()).getTrigger();            
            }
        }*/
        if (t.isString()) {
            return ASSyntax.parseTrigger(((StringTerm)t).getString());
        }
        if (t.isPlanBody()) {
            PlanBody p = (PlanBody)t;
            if (p.getPlanSize() == 1) {
                if (p.getBodyType() == BodyType.addBel)
                    return new Trigger(TEOperator.add, TEType.belief, (Literal)p.getBodyTerm());
                if (p.getBodyType() == BodyType.delBel)
                    return new Trigger(TEOperator.del, TEType.belief, (Literal)p.getBodyTerm());
            }
        }
        return null;
    }
    
    /** get as XML */
    public Element getAsDOM(Document document) {
        Element e = (Element) document.createElement("trigger");
        e.setAttribute("operator", operator.toString());
        e.setAttribute("type", type.toString());
        e.appendChild(literal.getAsDOM(document));
        return e;
    }

}
